
import datetime
import string

_NOTFOUND = object()

class Task(object):
    def __init__(self, taskString=None, id=-1):
        self.id = id
        self._completed = False
        self.dtCompleted = None
        self._priority = None
        self.dtCreated = datetime.date.today()
        self.text = ''
        self.projects = list()
        self.contexts = list()
        if taskString:
            self.dtCreated = None
            self.fromString(taskString)

    @property
    def completed(self):
        """Is the task completed"""
        return self._completed

    @completed.setter
    def completed(self, value):
        self._completed = bool(value)
        if self._completed:
            self.dtCompleted = datetime.date.today()
        else:
            self.dtCompleted = None

    @property
    def priority(self):
        '''what is the task's priority'''
        return self._priority

    @priority.setter
    def priority(self, value):
        if value in string.uppercase:
            self._priority = value
        else:
            raise ValueError('Priority must be A-Z')

    def fromString(self, tstring):
        """parse a given task string (tstring) and populate the task object.
        Implemented with an L(1) parser style."""

        tlist = tstring.split() #create a list of tokens
        ndx = 0                 #pointer to current token

        #look for completed flag and date
        if tlist[ndx] == 'x':
            #then the second element should parse as a date
            #http://stackoverflow.com/questions/127803/how-to-parse-iso-formatted-date-in-python
            try:
                self.completed = True
                self.dtCompleted = datetime.datetime.strptime(tlist[ndx+1],
                                                              "%Y-%m-%d").date()
                ndx += 2        #incr token pointer by 2 for both the x and the date
            except ValueError:
                #can only be completed if 'x YYYY-MM-DD ...'
                self.completed = False

        #look for priority information
        token = tlist[ndx]  #small optimization, mainly readability
        if token.startswith('(') and token.endswith(')') and len(token) == 3:
            priority = token[1:2]
            try:
                self.priority = priority
                ndx += 1    #look at the next element
            except ValueError:
                pass    #don't advance token pointer

        #Look for dtCreated information
        try:
            dtCreated = datetime.datetime.strptime(tlist[ndx], "%Y-%m-%d"
                                                   ).date()
            self.dtCreated = dtCreated
            ndx += 1
        except ValueError:
            pass

        #What remains should be task text rmingled with projects and contexts
        self.text = ' '.join(tlist[ndx:])

        for el in tlist[ndx:]:
            if len(el.strip())> 1:
                if el.startswith('+'):
                        self.projects.append(el)
                elif el.startswith('@'):
                    self.contexts.append(el)

    def toString(self):
        """format object in todo.txt format and return string"""
        tString = ''
        if self.completed:
            if self.dtCompleted:
                tString += 'x '
                tString += '%s ' % self.dtCompleted.strftime('%Y-%m-%d')
            else:
                raise ValueError('dtCompleted not set')

        if self.priority:
            tString += "(%s) " % self.priority

        if self.dtCreated:
            tString += "%s " % self.dtCreated.strftime('%Y-%m-%d')

        tString += self.text

        return tString

    def __eq__(self, other):
        for attr in ['completed', 'dtCompleted', 'priority', 'dtCreated', 'text', 'projects', 'contexts']:
            v1, v2 = [getattr(obj, attr, _NOTFOUND) for obj in [self, other]]
            if v1 is _NOTFOUND or v2 is _NOTFOUND:
                return False
            elif v1 != v2:
                return False
        return True

    #http://stackoverflow.com/questions/1436703/difference-between-str-and-repr-in-python
    def __repr__(self):
        """Present an unambigous, readable representation of the object"""
        return "<Task: id=%i, completed=%s/%s, priority=%s, dtCreated=%s, text=%s>" % (
                                                            self.id,
                                                            self.completed,
                                                            self.dtCompleted,
                                                            self.priority,
                                                            self.dtCreated,
                                                            self.text)

    def __str__(self):
        return self.toString()

    def _cmpval(self):
        '''sort on completed, priority or '[' char if not priority and finally text'''
        return [self.completed, self.priority or '[', self.text]

    def __lt__(self, other):
        return self._cmpval() < other._cmpval()
    def __gt__(self, other):
        return other._cmpval() < self._cmpval()
    def __le__(self, other):
        #not other < self
        return not other._cmpval() < self._cmpval()
    def __ge__(self, other):
        return not self._cmpval() < other._cmpval()
